﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using ScriptEngine.Script.Types;
using System.Dynamic;

namespace ScriptEngine.Script.Extension
{
	/// <summary>
	/// 为代码提示提供数据源
	/// </summary>
	[Serializable]
	public class CodeTipPojo
	{
		// 持有一个提示对象的实例，如果是方法的话则是MethodInfo
		public object Instance { get; }

		// 保存方法,对象注入信息的属性(其中Name属性一般为空,所以不可采用)
		public ScriptCallCsharp ScriptCallCsharp { get; }

		// 实际使用/注入的名称
		public string RealName { get; }

		// 表示该数据是否已经被废弃
		public bool IsObsolete { get; } = false;

		// 用于废弃该数据后显示的消息
		public string ObsoleteMsg { get; } = string.Empty;

		// 标注该方法或属性是否能用于代码提示
		public bool CanTip { get; } = true;

		// 该方法/属性隶属于的对象
		public object ParentObj { get; }

		public CodeTipPojo(object instance, ScriptCallCsharp scriptCallCsharp, string realName)
		{
			Instance = instance;
			ScriptCallCsharp = scriptCallCsharp;
			RealName = realName;
		}

		/// <summary>
		/// 该构造智能判断该数据源是否被废弃
		/// </summary>
		/// <param name="instance">该数据实例</param>
		/// <param name="scriptCallCsharp">脚本调用C#的属性实例</param>
		/// <param name="realName">数据注入到脚本引擎的真实标识符</param>
		/// <param name="obsolete">数据是否被废弃，可传入null</param>
		public CodeTipPojo(object parent, object instance, ScriptCallCsharp scriptCallCsharp, string realName, ObsoleteAttribute obsolete, bool canTip)
			: this(instance, scriptCallCsharp, realName)
		{
			if (obsolete != null)
			{
				IsObsolete = true;
				ObsoleteMsg = obsolete.Message;
			}

			ParentObj = parent;
			CanTip = canTip;
		}

		/// <summary>
		/// 将匹配到的代码提示转为用户易识别的格式
		/// </summary>
		/// <param name="codeTipPojo"></param>
		/// <returns></returns>
		public string CodeTipPojoToTipString()
		{
			switch (this.ScriptCallCsharp.Type)
			{
				case ExtensionType.Func:
					StringBuilder sb = new StringBuilder(this.RealName + "( ");
					MethodInfo methodInfo = this.Instance as MethodInfo;
					ParameterInfo[] parameters = methodInfo.GetParameters();
					foreach (var parameterInfo in parameters)
					{
						sb.Append(parameterInfo.ParameterType.ToString().Split('.').Last() + " ,");
					}

					if (sb.ToString().EndsWith(","))
					{
						sb.Remove(sb.Length - 1, 1);
					}

					sb.Append(") -> " + methodInfo.ReturnParameter.ParameterType.ToString().Split('.').Last());
					return sb.ToString();
				default:
					return this.RealName;
			}
		}
	}

	/// <summary>
	/// 提供给脚本编辑器环境的代码提示实体
	/// </summary>
	[Serializable]
	internal class CodeTipInstance
	{
		/// <summary>
		/// 代码提示的关键字
		/// </summary>
		[JsonProperty("caption")]
		public string Caption { get; private set; }

		/// <summary>
		/// 要提示的对象类型(function/object/const value ...)
		/// </summary>
		[JsonProperty("meta")]
		public string Meta { get; private set; }

		/// <summary>
		/// 非代码段匹配时用于匹配到替换的字符串
		/// </summary>
		[JsonProperty("value")]
		public string Value { get; private set; }

		/// <summary>
		/// 提示的类型: snippet/null
		/// </summary>
		[JsonProperty("type")]
		public string Type { get; private set; } = null;

		/// <summary>
		/// type为代码段时用于替换的代码段如log(${1:object})
		/// </summary>
		[JsonProperty("snippet")]
		public string Snippet { get; private set; }

		/// <summary>
		/// 用于提示的描述性文字
		/// </summary>
		public string TipText { get; private set; }

		/// <summary>
		/// 从一个codeTip中获取一组可用于编辑器提示的实体
		/// </summary>
		/// <param name="codeTip">代码提示实体</param>
		/// <param name="scriptEnv">脚本环境 js/lua</param>
		/// <returns>编辑器提示代码实体</returns>
		public static List<CodeTipInstance> OfCodeTipPojo(CodeTipPojo codeTip, ScriptType scriptType)
		{
			string methodSplit = scriptType == ScriptType.JAVASCRIPT ? "." : ":";
			List<CodeTipInstance> tipInstanceList = new List<CodeTipInstance>();
			if (!codeTip.CanTip && codeTip.ScriptCallCsharp.Type != ExtensionType.Object) return tipInstanceList;

			switch (codeTip.ScriptCallCsharp.Type)
			{
				case ExtensionType.Const:
					tipInstanceList.Add(SingleOfTipPoJo(codeTip));
					return tipInstanceList;
				case ExtensionType.Property:
					tipInstanceList.Add(SingleOfTipPoJo(codeTip));
					return tipInstanceList;
				case ExtensionType.Func:
					tipInstanceList.Add(MethodOfTipPoJo(codeTip));
					return tipInstanceList;
				case ExtensionType.NotFunc:
					if (codeTip.Instance.GetType().IsValueType)
					{
						tipInstanceList.Add(SingleOfTipPoJo(codeTip));
						return tipInstanceList;
					}
					goto default;
				default: break;
			}
			// 剩下则全为对象类型
			if (codeTip.CanTip)
			{
				tipInstanceList.Add(SingleOfTipPoJo(codeTip));
			}
			// 判断是否为扩展或枚举对象
			object curObj = codeTip.Instance is Enum ? ScriptEngineHelper.EnumToObject(codeTip.Instance as Enum) : codeTip.Instance;
			if (curObj is ExpandoObject extObj)
			{
				foreach (KeyValuePair<string, object> pair in extObj)
				{
					CodeTipPojo codeTipPojo = new CodeTipPojo(pair.Value, new ScriptCallCsharp(ExtensionType.NotFunc,
						statement: $"{codeTip.ScriptCallCsharp.Statement},值为: {pair.Value}"), $"{codeTip.RealName}.{pair.Key}");
					tipInstanceList.Add(SingleOfTipPoJo(codeTipPojo));
				}
			}
			else
			{
				/*** 否则为正常的注入对象 ***/

				ExtPropShowDitchObj propShowDitchObj = curObj.GetType().GetCustomAttribute<ExtPropShowDitchObj>();
				// 1.扫描公开属性
				foreach (PropertyInfo property in curObj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
				{
					if (property.GetCustomAttribute<CodeNotTip>() != null) continue;
					var obsoleteAttribute = property.GetCustomAttribute<ObsoleteAttribute>();
					var scriptCallCsharp = property.GetCustomAttribute<ScriptCallCsharp>() ?? new ScriptCallCsharp(ExtensionType.NotFunc,
						statement: $"{propShowDitchObj?.ParentDesc ?? codeTip.ScriptCallCsharp.Statement}中的属性");
					CodeTipPojo codeTipPojo = new CodeTipPojo(curObj, property.GetValue(curObj), scriptCallCsharp,
						$"{codeTip.RealName}.{property.Name}", obsoleteAttribute, true);
					// 属性不可脱离父对象
					if (propShowDitchObj == null || propShowDitchObj.IncludeParent)
					{
						tipInstanceList.Add(SingleOfTipPoJo(codeTipPojo));
					}
					// 属性可脱离父对象
					if (propShowDitchObj != null)
					{
						scriptCallCsharp = new ScriptCallCsharp(scriptCallCsharp.Type, scriptCallCsharp.Name,
							scriptCallCsharp.Statement + $"\n(位于{propShowDitchObj.ParentDesc ?? codeTip.RealName}对象中)");
						codeTipPojo = new CodeTipPojo(curObj, property.GetValue(curObj), scriptCallCsharp,
							$"{property.Name}", obsoleteAttribute, true);
						tipInstanceList.Add(SingleOfTipPoJo(codeTipPojo));
					}
				}

				// 2.扫描公共方法
				ExtMethodShowDitchObj methodShowDitchObj = curObj.GetType().GetCustomAttribute<ExtMethodShowDitchObj>();
				foreach (MethodInfo method in curObj.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance))
				{
					if (method.GetCustomAttribute<CodeNotTip>() != null) continue;
					string[] innerMethodName = { "GetType", "GetHashCode", "Equals", "ToString" };
					bool toNext = false;
					foreach (var limitName in innerMethodName)
					{
						if (limitName == method.Name)
						{
							toNext = true;
							continue;
						}
					}
					if (toNext) continue;
					var obsoleteAttribute = method.GetCustomAttribute<ObsoleteAttribute>();
					var scriptCallCsharp = method.GetCustomAttribute<ScriptCallCsharp>() ?? new ScriptCallCsharp(ExtensionType.Func,
						statement: $"{codeTip.ScriptCallCsharp.Statement}中的方法");
					CodeTipPojo codeTipPojo = new CodeTipPojo(curObj, method, scriptCallCsharp,
						$"{codeTip.RealName}{methodSplit}{method.Name}", obsoleteAttribute, true);
					// 不包含父元素的时候可不加入至方法提示
					if (methodShowDitchObj == null || methodShowDitchObj.IncludeParent)
					{
						tipInstanceList.Add(MethodOfTipPoJo(codeTipPojo));
					}
					// 方法可脱离父对象显示
					if (methodShowDitchObj != null)
					{
						scriptCallCsharp = method.GetCustomAttribute<ScriptCallCsharp>();
						if (scriptCallCsharp == null)
						{
							scriptCallCsharp = new ScriptCallCsharp(ExtensionType.Func,
								statement: $"{codeTip.ScriptCallCsharp.Statement}中的方法");
						}
						else
						{
							scriptCallCsharp = new ScriptCallCsharp(ExtensionType.Func,
								statement: scriptCallCsharp.Statement + $"\n(位于{methodShowDitchObj.ParentDesc ?? codeTip.RealName}对象中)");
						}
						codeTipPojo = new CodeTipPojo(curObj, method, scriptCallCsharp,
							method.Name, obsoleteAttribute, true);
						tipInstanceList.Add(MethodOfTipPoJo(codeTipPojo));
					}
				}
			}
			return tipInstanceList;
		}

		/// <summary>
		/// 从方法类型的codeTip中获取CodeTipInstance
		/// </summary>
		/// <param name="codeTip"></param>
		/// <returns></returns>
		private static CodeTipInstance MethodOfTipPoJo(CodeTipPojo codeTip)
		{
			string tipStr = codeTip.ScriptCallCsharp.Statement ?? "";
			if (codeTip.IsObsolete)
			{
				tipStr = $"[已弃用: {codeTip.ObsoleteMsg}]\n" + tipStr;
			}
			CodeTipInstance tipInstance = new CodeTipInstance()
			{
				Caption = codeTip.RealName,
				Meta = GetMetaDesc(codeTip),
				Type = "snippet",
				TipText = tipStr
			};
			StringBuilder sb = new StringBuilder(codeTip.RealName);
			sb.Append('(');
			MethodInfo methodInfo = codeTip.Instance as MethodInfo;
			int index = 1;
			for (var i = 0; i < methodInfo.GetParameters().Length; i++)
			{
				ParameterInfo parameter = methodInfo.GetParameters()[i];
				if (parameter.IsOut)
				{
					continue;
				}
				sb.Append("${").Append($"{index++}:{parameter.Name}").Append("}, ");
			}

			if (index > 1)
			{
				sb.Remove(sb.Length - 2, 2);
			}

			sb.Append(')');
			tipInstance.Snippet = sb.ToString();
			return tipInstance;
		}

		/// <summary>
		/// 从值类型的codetip中获取单一CodeTipInstance
		/// </summary>
		/// <param name="codeTip">代码提示实体</param>
		/// <returns>CodeTipInstance</returns>
		private static CodeTipInstance SingleOfTipPoJo(CodeTipPojo codeTip)
		{
			string tipStr = codeTip.ScriptCallCsharp.Statement ?? "";
			if (codeTip.IsObsolete)
			{
				tipStr = $"[已弃用: {codeTip.ObsoleteMsg}]\n" + tipStr;
			}
			return new CodeTipInstance
			{
				Caption = codeTip.RealName,
				Meta = GetMetaDesc(codeTip),
				Value = codeTip.RealName,
				Snippet = codeTip.RealName,
				TipText = tipStr
			};
		}

		/// <summary>
		/// 获取代码提示的类型描述
		/// </summary>
		/// <param name="codeTip">代码提示</param>
		/// <returns>类型描述</returns>
		private static string GetMetaDesc(CodeTipPojo codeTip)
		{
			ScriptCallCsharp scriptCallCsharp = codeTip.ScriptCallCsharp;
			string meta = scriptCallCsharp.GetTypeName();
			if (scriptCallCsharp.Type == ExtensionType.NotFunc)
			{
				ExtensionType type = codeTip.Instance.GetType().IsValueType || codeTip.Instance is string
					? ExtensionType.Const
					: ExtensionType.Object;
				meta = ScriptCallCsharp.GetTypeName(type);

			}
			StringBuilder sb = new StringBuilder(meta);
			if (codeTip.IsObsolete)
			{
				sb.Append("(已弃用)");
			}
			return sb.ToString();
		}

		public override string ToString()
		{
			return $"{nameof(Caption)}: {Caption}, {nameof(Meta)}: {Meta}, {nameof(Value)}: {Value}, {nameof(Type)}: {Type}, {nameof(Snippet)}: {Snippet}, {nameof(TipText)}: {TipText}";
		}
	}
}
